// Package irisweb 是对 iris 的基本再封装
// TODO: 有很多特性没处理，如果需要特别处理，可自行拿到 wa.App 进行处理
// TODO: 分包有些不正确，写急了，哪位有空可以再调
package irisweb

import (
	"cm2"
	"cm2/exception"
	"cm2/util"
	stdContext "context"
	"os"
	"os/signal"
	"path"
	"strings"
	"syscall"
	"time"

	"github.com/go-xorm/xorm"
	"github.com/iris-contrib/middleware/cors"
	"github.com/kataras/iris"
	"github.com/kataras/iris/middleware/logger"
	rotatelogs "github.com/lestrrat-go/file-rotatelogs"
)

// HandlerOption is config struct for irisweb app context handler
type HandlerOption struct {
	HandlerFunc func(ctx iris.Context, app *Webapp) (interface{}, *exception.Exception)
	Method      string
	Path        string
}

// CorsOption from github.com/iris-contrib/middleware/cors redefined for irisweb app context handler
type CorsOption struct {
	// AllowedOrigins is a list of origins a cross-domain request can be executed from.
	// If the special "*" value is present in the list, all origins will be allowed.
	// An origin may contain a wildcard (*) to replace 0 or more characters
	// (i.e.: http://*.domain.com). Usage of wildcards implies a small performance penalty.
	// Only one wildcard can be used per origin.
	// Default value is ["*"]
	AllowedOrigins []string
	// AllowOriginFunc is a custom function to validate the origin. It take the origin
	// as argument and returns true if allowed or false otherwise. If this option is
	// set, the content of AllowedOrigins is ignored.
	AllowOriginFunc func(origin string) bool
	// AllowedMethods is a list of methods the client is allowed to use with
	// cross-domain requests. Default value is simple methods (HEAD, GET and POST).
	AllowedMethods []string
	// AllowedHeaders is list of non simple headers the client is allowed to use with
	// cross-domain requests.
	// If the special "*" value is present in the list, all headers will be allowed.
	// Default value is [] but "Origin" is always appended to the list.
	AllowedHeaders []string
	// ExposedHeaders indicates which headers are safe to expose to the API of a CORS
	// API specification
	ExposedHeaders []string
	// MaxAge indicates how long (in seconds) the results of a preflight request
	// can be cached
	MaxAge int
	// AllowCredentials indicates whether the request can include user credentials like
	// cookies, HTTP authentication or client side SSL certificates.
	AllowCredentials bool
	// OptionsPassthrough instructs preflight to let other potential next handlers to
	// process the OPTIONS method. Turn this on if your application handles OPTIONS.
	OptionsPassthrough bool
	// Debugging flag adds additional output to debug server side CORS issues
	Debug bool
}

// Webapp create a irisWebApp wrapper
type Webapp struct {
	Name         string
	App          *iris.Application
	Address      string
	shuttingDown chan bool
	db           *xorm.Engine
}

// New create a new IrisWebApp instance
func New(name string) *Webapp {
	wa := &Webapp{
		Name:    name,
		App:     iris.New(),
		Address: ":8070",
	}

	return wa
}

// SetAddr set listern address
func (wa *Webapp) SetAddr(addr string) {
	if addr == "" {
		addr = ":8070"
	}
	wa.Address = addr
}

// SetDBProvider 设置数据库提供程序
func (wa *Webapp) SetDBProvider(db *xorm.Engine) {
	wa.db = db
}

// Logger init logger level
func (wa *Webapp) Logger(lname, llvl, lpath string) {
	if llvl == "" {
		llvl = "info"
	}
	wa.App.Logger().SetLevel(llvl)

	if util.GetGOENV() != cm2.DEV {
		// 初始化 loggerWriter
		logRoot := path.Join(util.GetWorkingPath(), lpath)
		util.Mkdir(logRoot)
		baseLogName := path.Join(logRoot, lname)
		writer, _ := rotatelogs.New(
			baseLogName+"%Y-%m-%d"+".log",
			// rotatelogs.WithLinkName(basePath),
			rotatelogs.WithMaxAge(604800),
			rotatelogs.WithRotationTime(86400),
		)
		wa.App.Logger().SetOutput(writer)
	} else {
		wa.App.Logger().SetOutput(os.Stdout)
	}

	wa.App.Use(logger.New())
}

// RegisterDefaultErrorHandler 注册默认的错误处理程序
func (wa *Webapp) RegisterDefaultErrorHandler() {
	wa.RegisterErrorHandler(DefaultOnAnyErrorCodeHandler)
}

// RegisterErrorHandler 注册指定的错误处理程序
func (wa *Webapp) RegisterErrorHandler(handler func(iris.Context)) {
	wa.App.OnAnyErrorCode(handler)
}

// RegisterDefaultRecoverHandler 注册默认的恢复处理程序，以处理未处理的error 和 panic
func (wa *Webapp) RegisterDefaultRecoverHandler() {
	wa.RegisterRecoverHandler(NewRecoverMiddleware())
}

// RegisterRecoverHandler 注册指定的恢复处理程序，以处理未处理的error 和 panic
func (wa *Webapp) RegisterRecoverHandler(handler func(iris.Context)) {
	wa.App.Use(handler)
}

// RegisterArea 注册一个area
// TODO: 如果需要高级请使用 wa.App 进行 iris原生处理
func (wa *Webapp) RegisterArea(areaPath string, handlers []HandlerOption) iris.Party {
	areaParty := wa.App.Party(areaPath, cors.New(cors.Options{})).AllowMethods(iris.MethodOptions)

	wa.registerRouteHandlersToArea(areaParty, handlers)

	return areaParty
}

// RegisterAreaWithCors 指定 cors 注册一个area
// TODO: 如果需要高级请使用 wa.App 进行 iris原生处理
func (wa *Webapp) RegisterAreaWithCors(areaPath string, handlers []HandlerOption, corsOption CorsOption) iris.Party {
	areaParty := wa.App.Party(areaPath, cors.New(cors.Options{
		AllowedOrigins:     corsOption.AllowedOrigins,
		AllowOriginFunc:    corsOption.AllowOriginFunc,
		AllowedMethods:     corsOption.AllowedMethods,
		AllowedHeaders:     corsOption.AllowedHeaders,
		ExposedHeaders:     corsOption.ExposedHeaders,
		MaxAge:             corsOption.MaxAge,
		AllowCredentials:   corsOption.AllowCredentials,
		OptionsPassthrough: corsOption.OptionsPassthrough,
		Debug:              corsOption.Debug,
	})).AllowMethods(iris.MethodOptions)

	wa.registerRouteHandlersToArea(areaParty, handlers)

	return areaParty
}

// Start 启动 App，TODO: 简单调用。后面的参数我也不知道怎么用...深入了再来
func (wa *Webapp) Start() {
	go func() {
		ch := make(chan os.Signal, 1)
		signal.Notify(ch,
			// kill -SIGINT XXXX or Ctrl+c
			os.Interrupt,
			syscall.SIGINT, // register that too, it should be ok
			// os.Kill  is equivalent with the syscall.Kill
			os.Kill,
			syscall.SIGKILL, // register that too, it should be ok
			// kill -SIGTERM XXXX
			syscall.SIGTERM,
		)
		select {
		case <-ch:
			wa.shutDown()
		}
	}()
	// 自定义关闭命令
	go func() {
		wa.shuttingDown = make(chan bool, 1)
		select {
		case <-wa.shuttingDown:
			wa.shutDown()
		}
	}()

	wa.App.Logger().Infof("%s's listerning on %s", wa.Name, wa.Address)
	wa.App.Run(iris.Addr(wa.Address), iris.WithoutInterruptHandler)
}

// Shutdown 发送关闭消息
func (wa *Webapp) Shutdown() {
	if wa.shuttingDown == nil {
		return
	}
	if len(wa.shuttingDown) >= 1 {
		return
	}
	wa.shuttingDown <- true
}

func (wa *Webapp) shutDown() {
	wa.App.Logger().Infof("%s's shutting down...", wa.Name)

	timeout := 5 * time.Second
	ctx, cancel := stdContext.WithTimeout(stdContext.Background(), timeout)
	defer cancel()
	wa.App.Shutdown(ctx)
}

// registerRouteHandlersToArea 以外层一次简单封装实现基本JSON输出和错误捕捉
func (wa *Webapp) registerRouteHandlersToArea(areaParty iris.Party, handlers []HandlerOption) {
	if handlers == nil {
		return
	}

	wrapperFn := func(fn func(ctx iris.Context, app *Webapp) (interface{}, *exception.Exception)) func(ctx iris.Context) {
		return func(ctx iris.Context) {
			rtn, exp := fn(ctx, wa)

			if exp != nil {
				ctx.StatusCode(500)
				ctx.Values().Set("__eCode", exp.Code)
				ctx.Values().Set("__eName", exp.Name)
				ctx.Values().Set("__eMessage", exp.Message)
			} else {
				WriteJSON(ctx, rtn)
			}
		}
	}

	for _, option := range handlers {
		switch strings.ToUpper(option.Method) {
		case MethodGet:
			areaParty.Get(option.Path, wrapperFn(option.HandlerFunc))
			break
		case MethodPost:
			areaParty.Post(option.Path, wrapperFn(option.HandlerFunc))
			break
		case MethodPut:
			areaParty.Put(option.Path, wrapperFn(option.HandlerFunc))
			break
		case MethodDelete:
			areaParty.Delete(option.Path, wrapperFn(option.HandlerFunc))
			break
		case MethodConnect:
			areaParty.Connect(option.Path, wrapperFn(option.HandlerFunc))
			break
		case MethodHead:
			areaParty.Head(option.Path, wrapperFn(option.HandlerFunc))
			break
		case MethodPatch:
			areaParty.Patch(option.Path, wrapperFn(option.HandlerFunc))
			break
		case MethodOptions:
			areaParty.Options(option.Path, wrapperFn(option.HandlerFunc))
			break
		case MethodTrace:
			areaParty.Trace(option.Path, wrapperFn(option.HandlerFunc))
			break
		}
	}
}
